package nz.co.anameg.otp;

import java.security.MessageDigest;

/**
 * This class provides a One Time Password Server check, it can be used with the iPhone iOTP app or similar.
 * <p>
 * Note that this functionality is not thread safe.
 * </p>
 * <p>
 * Copyright (c) 2010, Anameg Consulting Ltd
 * </p>
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this
 * permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * </p>
 * 
 * @author Brent
 */
public class OTP
{
    private final MessageDigest md5;
    private int gracePeriod = 3;

    /**
     * The constructor defaults the gracePeriod to +/- 3 mins.
     */
    public OTP()
    {
        try
        {
            md5 = MessageDigest.getInstance("MD5");
        }
        catch (Exception e)
        {
            throw new OTPException(e);
        }
    }

    /**
     * This returns the epoch and can be used to check the time compared to the client.
     * 
     * @return Current Epoch String.
     */
    public String getEpoch()
    {
        return timeToEpoch(getTimeInSeconds());
    }

    /**
     * This returns true if the three string are correct.
     * 
     * @param initialSecret
     *            This is the initial secret string which would normally be stored in the database.
     * @param pin
     *            This is the users PIN that would come from the database. This is normally a number.
     * @param otp
     *            This is the 6 digit string that the user has obtained from the client program.
     * @return True if the OTP is valid with in the timeperiod.
     */
    public boolean checkOTP(String initialSecret, String pin, String otp)
    {
        try
        {
            long period = gracePeriod * 60;
            long epoch = getTimeInSeconds();
            for (long i = (epoch - period); i <= (epoch + period); i++)
            {
                byte[] bs = md5.digest((timeToEpoch(i) + initialSecret + pin).getBytes());
                String s = getHexString(bs);
                s = s.substring(0, 6);
                if (otp.equalsIgnoreCase(s))
                {
                    return true;
                }
            }
            return false;
        }
        catch (Exception e)
        {
            throw new OTPException("Problem check OTP", e);
        }
    }

    // 
    // Internal methods...
    //

    /**
     * Formats the epoch string for the time given.
     * 
     * @param tm
     *            Time in Seconds since Epoch...
     * @return Formatted epoch string
     */
    private String timeToEpoch(long tm)
    {
        String stm = String.valueOf(tm);
        return stm.substring(0, stm.length() - 1);
    }

    private long getTimeInSeconds()
    {
        return System.currentTimeMillis() / 1000;
    }

    private String getHexString(byte[] b) throws Exception
    {
        String result = "";
        for (int i = 0; i < b.length; i++)
        {
            result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1);
        }
        return result;
    }

    //
    // Standard Getters and Setters
    //

    public void setGracePeriod(int gracePeriod)
    {
        this.gracePeriod = gracePeriod;
    }

    public int getGracePeriod()
    {
        return gracePeriod;
    }

}
